<?php

// declare(strict_types=1);

namespace app\common;

use think\File;
use think\image\Exception as ImageException;
use think\Image;
use app\common\AliYunOss\Image as AliYunOssImage;
use app\common\model\Images;

class ImageHandle
{
    protected $aliyun_oss;
    protected $image;
    protected $oss_domain_name;

    public function __construct()
    {
        $this->aliyun_oss = new AliyunOssImage();
        $this->oss_domain_name = 'https://' . config('filesystem.aliyun-oss.bucket') . '.' . config('filesystem.aliyun-oss.endpoint');
        $this->image = [
            'limit' => [
                'type_limit' => explode(',', config('filesystem.file.image.type_limit')),
                'max_size_limit' => config('filesystem.file.image.max_size_limit')
            ],
            'temp_path' => root_path() . 'public/static/uploads/temp/'
        ];
    }

    /**
     * @describe 生成唯一的文件名。
     * @author: Bobo
     * @date: 2024-06-11 03:39:08 
     * @param string $type 文件的后缀名，必须是字母或数字的组合。  @required
     * @return string |Exception  返回一个唯一且带有指定后缀的文件或者抛出异常。
     */
    public function createFileName(string $type): string
    {

        if (!preg_match('/^[a-zA-Z0-9]+$/', $type)) {
            throw new \Exception('非法文件后缀名');
        }
        return date('YmdHis') .  ceil(microtime(true) * 1000) .  bin2hex(random_bytes(5)) . '.' . $type;
    }

    /**
     * @describe
     * @author: Bobo
     * @date: 2024-06-11 23:00:51 
     * @param string $fileName  文件名称 @required
     * @param string $type 操作类型，默认为空，
     * @return string|Exception 构建的文件路径或异常
     */
    public function createOssFilePath(string $fileName, string $directory, string $type = ''): string
    {
        $preg_match = '/[^\w\.]/';
        if (!$fileName || preg_match($preg_match, $fileName)) {
            throw new \Exception("文件名称非法，不可包含特殊符号");
        }
        if (!$directory || preg_match($preg_match, $directory)) {
            throw new \Exception("目录名称非法，不可包含特殊符号");
        }

        $filePath = $directory . '/' . date('Ym') . '/' . date('d');
        switch ($type) {
            case 'thumb':
                $filePath .= '/thumb/thumb_' . $fileName;
                break;
            case 'water':
                $filePath .= '/water/water_' . $fileName;
                break;

            default:
                $filePath .= '/' . $fileName;
                break;
        }
        return $filePath;
    }

    /**
     * @describe base64  图片上传
     * @author: Bobo
     * @date: 2024-06-07 15:26:34 
     * @param string $base64Image   base64图片字符串 @required
     * @return arr 包含上传结果和消息的数组。
     *               - 'url': 文件在OSS上的URL。
     *               - 'result': 上传结果，true为成功，false为失败。
     *               - 'message': 上传消息，成功时为"上传成功"，失败时为错误信息。
     */
    public function uploadImgByBase64(string $base64Image, int $directory, string $alt = ''): array
    {
        $return = [
            'url' => '',
            'result' => false,
            'message' => ''
        ];
        $fileSize = strlen($base64Image);

        if ($fileSize > (int)$this->image['limit']['max_size_limit']) {
            $return['message'] = '文件大小超过限制';
            return $return;
        }
        $tempFilePath = $this->image['temp_path']; // 临时文件保存路径
        $base64ToImageResult = $this->base64ToImage($base64Image, $tempFilePath); // 保存图片到临时文件

        if (!$base64ToImageResult['result']) {
            $return['message'] = $base64ToImageResult['message'];
            return $return;
        }

        $tempFile = $base64ToImageResult['url'];
        try {
            $type = pathinfo($tempFile, PATHINFO_EXTENSION);
            $file = new File($tempFile);
            // 上传图片到指定目录
            $fileName = $this->createFileName($type);
            $directoryValue = config('filesystem.file.image.directory')[$directory];

            $filePath = $this->createOssFilePath($fileName, $directoryValue);

            $result =  $this->aliyun_oss->uploadFile($filePath, $file);
            // 删除临时文件
            unlink($tempFile);
            if (!$result) {
                $return['message'] = '上传失败';
                return $return;
            }
            try {
                $imageModel = new Images();
                $url =  '/' . $filePath;
                $imageModel->url = $url;
                $imageModel->directory = $directory;


                if ($alt) {
                    $imageModel->alt = $alt;
                }

                $imageModel->save();

                $return['url'] = $this->oss_domain_name . '/' . $filePath;
                $return['id'] = $imageModel->id;
                $return['result'] = true;
                $return['message'] = '上传成功';
            } catch (\Exception $e) {
                $return['result'] = false;
                $return['message'] = '上传失败：' . $e->getMessage();
            }
            return $return;
        } catch (\Exception $e) {
            $return['message'] = $e->getMessage();
            return $return;
        }
    }


    /**
     * @describe base64  图片生成缩略图
     * @author: Bobo
     * @date: 2024-06-11 04:15:30 
     * @param string $base64Image  base64编码的图片数据  @required
     * @param int $thumbWidth 缩略图宽度  @required
     * @return array 返回包含结果、URL和消息的数组
     */
    public function base64ImageGenerateThumb(string $base64Image, int $thumbWidth): array
    {
        $return = [
            'url' => '',
            'result' => false,
            'message' => ''
        ];

        // 参数验证
        if (empty($base64Image) || $thumbWidth <= 0) {
            $return['message'] = '无效的参数';
            return $return;
        }

        $fileSize = strlen($base64Image);
        if ($fileSize > (int)$this->image['limit']['max_size_limit']) {
            $return['message'] = '文件大小超过限制';
            return $return;
        }

        $tempFilePath = $this->image['temp_path']; // 临时文件保存路径
        $base64ToImageResult = $this->base64ToImage($base64Image, $tempFilePath); // 保存图片到临时文件

        if (!$base64ToImageResult['result']) {
            $return['message'] = $base64ToImageResult['message'];
            return $return;
        }

        try {
            $tempFile = $base64ToImageResult['url'];

            $type = pathinfo($tempFile, PATHINFO_EXTENSION);
            $fileName = $this->createFileName($type);
            $thumbFilePath = $this->createOssFilePath($fileName, 'thumb');
            $thumbResult = $this->generateThumb($tempFile, $thumbWidth);
            unlink($tempFile);
            if (!$thumbResult['result']) {
                return $thumbResult;
            }
            $return['url'] = $this->oss_domain_name . '/' . $thumbFilePath;
            $return['result'] = true;
            $return['message'] = 'Successful operation';
            return $return;
        } catch (\Exception $e) {
            $return['message'] = $e->getMessage();
            return $return;
        }
    }



    /**
     * @describe: 将Base64编码的图片数据转换为图像文件并保存。
     * 此函数接受一个Base64编码的图片字符串，验证其有效性，并将其转换为对应的图像文件。
     * 如果提供了URL和名称，则将文件保存到指定的路径和名称；否则，使用默认的路径和生成的名称。
     * 函数返回一个数组，包含操作结果、URL和消息字段。
     * @author: Bobo
     * @date: 2024-06-11 03:27:22 
     * @param string $base64Image @required  Base64编码的图片数据。
     * @param string $url 保存图像文件的目录路径，默认为空，使用预设路径
     * @param string $name 保存图像文件的名称，默认为空，使用默认名称
     * @return array 返回一个数组，包含url、result和message字段。
     */
    public function base64ToImage(string $base64Image, string $url = '', string $name = ''): array
    {

        $return = [
            'url' => '',
            'result' => false,
            'message' => ''
        ];
        if (empty($base64Image)) {
            $return['message'] = '没有接收到图片数据';
            return $return;
        }
        $fileSize = strlen($base64Image);
        if ($fileSize > (int)$this->image['limit']['max_size_limit']) {
            $return['message'] = '文件大小超过限制';
            return $return;
        }
        try {
            // 解析 Base64 编码的图片数据
            if (preg_match('/^data:image\/(\w+);base64,/', $base64Image, $type)) {
                $data = substr($base64Image, strpos($base64Image, ',') + 1);
                $type = strtolower($type[1]); // jpg, png, gif 等
                if (!in_array($type, $this->image['limit']['type_limit'])) {
                    $return['message'] = '不支持的图片格式';
                    return $return;
                }
                $data = base64_decode($data);
                if ($data === false) {
                    $return['message'] = 'Base64 解码失败';
                    return $return;
                }
            } else {
                $return['message'] = '不支持的图片类型';
                return $return;
            }

            // 保存图片到文件
            if (empty($url)) $url = $this->image['temp_path'];
            if (!is_dir($url)) {
                mkdir($url, 0777, true);
            }
            if (empty($name)) $name = $this->createFileName($type);

            $tempFile = $url . $name;

            file_put_contents($tempFile, $data);
            $return['url'] = $tempFile;
            $return['result'] = true;
            $return['message'] = 'Successful operation';
            return $return;
        } catch (\Exception $e) {
            $return['message'] = $e->getMessage();
            return $return;
        }
    }

    /**
     * 缩放图片 该方法用于根据指定的宽度对图片进行缩放，并将缩放后的图片上传到阿里云OSS。
     * 如果缩放过程或上传过程中发生错误，将返回相应的错误信息。
     *
     * @author: Bobo
     * @date: 2024-06-11 03:37:28 
     * @param string $filePath  文件路径 图片在本地的文件路径。 @required
     * @param string $ossFileName  OSS文件名阿里云OSS上目标图片的文件名。 @required
     * @param int $thumbWidth  缩放后的宽度 指定缩放后的图片宽度，程序将根据此宽度自动计算高度。 @required
     * @return array 返回操作结果
     *         返回一个包含操作结果和消息的数组，如果操作失败，'result'为false，'message'包含错误消息；
     *         如果操作成功，'result'为true，'url'包含缩放后图片的URL地址。
     */
    public function generateThumb($filePath, int $thumbWidth): array
    {
        $return = [
            'url' => '',
            'result' => false,
            'message' => ''
        ];

        // 检查文件是否存在、可读且是文件
        if (!file_exists($filePath) || !is_readable($filePath) || !is_file($filePath)) {
            $return['message'] = '文件不存在或无法读取';
            return $return;
        }


        // 检查缩略图宽度是否为正整数
        if (!is_int($thumbWidth) || $thumbWidth <= 0) {
            $return['message'] = '缩放宽度必须为正整数';
            return $return;
        }

        // 检查文件大小是否超过限制
        $fileSize = filesize($filePath);
        if ($fileSize > (int)$this->image['limit']['max_size_limit']) {
            $return['message'] = '文件大小超过限制';
            return $return;
        }

        $tempFilePath = $this->image['temp_path']; // 临时文件保存路径

        // 确保目录存在
        if (!is_dir($tempFilePath)) {
            mkdir($tempFilePath, 0777, true);
        }

        try {
            $image = Image::open($filePath);
            $type = $image->type();

            $thumbFileName = 'thumb' . $this->createFileName($type); // 缩略图文件名
            bcscale(0); // 设置小数点后保留两位小数
            // 计算缩略图的高度，保持图片比例
            $originalWidth = round($image->width(), 0);
            $originalHeight = round($image->height(), 0);
            $thumbHeight = round(($thumbWidth * $originalHeight) / $originalWidth, 0);

            // 生成缩略图并保存
            $image->thumb($thumbWidth, $thumbHeight, Image::THUMB_CENTER)->save($tempFilePath . $thumbFileName);

            $thumbFile = new File($tempFilePath . $thumbFileName);

            return [
                'code' => 0,
                'file' => $thumbFile
            ];
        } catch (\Exception $e) {
            $return['code'] = 100045;
            $return['message'] = '生成缩略图时发生错误: ' . $e->getMessage();
            return $return;
        }
    }


    /**
     * 添加水印
     *
     * @param string $filePath 文件路径
     * @param string $watermark 水印图片路径
     * @param int $position 位置
     * @param int $opacity 透明度
     * @return bool 成功返回true，失败返回false
     */
    public function addImageWatermark($filePath, $watermark, $position = Image::WATER_SOUTHEAST, $opacity = 100) {}
    /**
     * @describe base64图片添加文字水印
     * @author: Bobo
     * @date: 2024-06-12 19:40:44 
     * @param string $base64Image base64图片字符串      @required 
     * @param string $text 水印文字                     @required 
     * @param int    $fontSize 字号
     * @param string $color 文字颜色
     * @param string $font 字体文件路径
     * @param int    $position 水印位置
     * @param int    $offset 文字相对当前位置的偏移量
     * @param int    $angle  文字倾斜角度
     * @return array
     */
    public function base64ImageGenerateTextWatermark(
        string $base64Image,
        string $text,
        int $fontSize = 20,
        string $color = '#000000',
        string $font = '',
        int $position = Image::WATER_SOUTHEAST,
        int $offset = 0,
        int $angle = 0
    ): array {
        // 参数验证
        if (empty($base64Image) || empty($text)) {
            $return['message'] = '无效的参数';
            return $return;
        }
        $fileSize = strlen($base64Image);

        if ($fileSize > (int)$this->image['limit']['max_size_limit']) {
            $return['message'] = '文件大小超过限制';
            return $return;
        }
        $tempFilePath = $this->image['temp_path']; // 临时文件保存路径
        $base64ToImageResult = $this->base64ToImage($base64Image, $tempFilePath); // 保存图片到临时文件

        if (!$base64ToImageResult['result']) {
            $return['message'] = $base64ToImageResult['message'];
            return $return;
        }
        $tempFile = $base64ToImageResult['url'];

        try {
            $type = pathinfo($tempFile, PATHINFO_EXTENSION);
            $fileName = $this->createFileName($type);
            $ossWaterFilePath = $this->createOssFilePath($fileName, 'thumb');
            $waterResult = $this->generateTextWatermark($tempFile, $text, $ossWaterFilePath, $fontSize, $color, $font, $position, $offset, $angle);
            unlink($tempFile);
            if (!$waterResult['result']) {
                return $waterResult;
            }
            $return['url'] = $this->oss_domain_name . '/' . $ossWaterFilePath;
            $return['result'] = true;
            $return['message'] = 'Successful operation';
            return $return;
        } catch (\Throwable $e) {
            //throw $th;
            $return['message'] = '添加水印失败: ' . $e->getMessage();
            return $return;
        }
    }

    /**
     * @describe 给图片添加文字水印
     * @author: Bobo
     * @date: 2024-06-12 19:40:44 
     * @param string $sourceImagePath 源图片路径
     * @param string $text 水印文字
     * @param string $ossFileName oss文件名
     * @param int    $fontSize 字号、
     * @param string $color 文字颜色
     * @param string $font 字体文件路径
     * @param int    $position 水印位置
     * @param int    $offset 文字相对当前位置的偏移量
     * @param int    $angle  文字倾斜角度
     * @return array
     */
    public function generateTextWatermark(
        string $sourceImagePath,
        string $text,
        string $ossFileName,
        int $fontSize = 20,
        string $color = '#000000',
        string $font = '',
        int $position = Image::WATER_SOUTHEAST,
        int $offset = 0,
        int $angle = 0
    ): array {
        $return = [
            'result' => false,
            'message' => '',
            'url' => ''
        ];
        if (empty($font)) $font =  root_path() . 'public/static/font/朱雀仿宋.ttf';
        if (
            !file_exists($sourceImagePath)
            || !is_readable($sourceImagePath)
            || !is_file($sourceImagePath)
        ) {
            $return['message'] = '文件不存在或无法读取';
            return $return;
        }
        if (empty($text)) {
            $return['message'] = '水印文字不能为空';
            return $return;
        }
        if (empty($ossFileName)) {
            $return['message'] = 'OSS文件名不能为空';
            return $return;
        }
        $type = pathinfo($ossFileName, PATHINFO_EXTENSION);

        if (!in_array($type, $this->image['limit']['type_limit'])) {
            $return['message'] = '不支持的图片格式';
            return $return;
        }

        if (!is_file($font)) {
            $return['message'] = '字体文件不存在';
            return $return;
        }

        $fileSize = filesize($sourceImagePath);

        if ($fileSize > (int)$this->image['limit']['max_size_limit']) {
            $return['message'] = '文件大小超过限制';
            return $return;
        }


        $tempPath = $this->image['temp_path'];
        if (!is_dir($tempPath)) mkdir($tempPath, 0777, true);
        try {
            // 打开图片
            $image = Image::open($sourceImagePath);

            // 添加文字水印
            $image->text($text, $font, $fontSize, $color, $position, $offset, $angle);

            $fileName = 'watermark_' . $this->createFileName($image->type());
            $filePath = $tempPath . $fileName;
            // 保存图片
            $image->save($filePath);

            $result =  $this->aliyun_oss->uploadFile($ossFileName, $filePath);
            // 删除临时文件
            unlink($filePath);

            if (!$result) {
                $return['result'] = false;
                $return['message'] = '添加水印失败';
                return $return;
            } else {
                $return['url'] = $this->oss_domain_name . '/' . $ossFileName;
                $return['result'] = true;
                $return['message'] = '水印添加成功';
                return $return;
            }
        } catch (\Exception $e) {
            $return['message'] = '添加水印失败: ' . $e->getMessage();
            return $return;
        }
    }
}
